import base64
import pickle
from django_redis import get_redis_connection
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView

# Create your views here.
from carts.serializers import CartSerializer, CartSKUSerializer, CartDeleteSerializer, CartSelectAllSerializer
from goods.models import SKU


class CartView(APIView):
    """购物车增删改查"""

    def perform_authentication(self, request):
        """重写此方法可以延后/禁用认证"""
        # 当第一次写 request.user /request.auth就会再去执行认证逻辑
        pass

    def post(self, request):
        """购物车新增"""

        # 创建序列化器进行反序列化
        serializer = CartSerializer(data=request.data)
        # 调用序列化器的校验方法
        serializer.is_valid(raise_exception=True)
        # 获取反序列化校验后的数据
        sku_id = serializer.validated_data.get('sku_id')
        count = serializer.validated_data.get('count')
        selected = serializer.validated_data.get('selected')

        try:
            user = request.user  # 当执行到此行代码时还是会认证,但未认证用户会抛出401,自己捕获不让他提前响应
        except:
            user = None

        response = Response(serializer.data, status=status.HTTP_201_CREATED)  # 下面的提上来,优化

        # user.is_authenticated() 判断是登录用户还是匿名用户
        if user:
            # 登陆用户操作redis购物车数据
            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')
            # 创建管道
            pl = redis_conn.pipeline()
            """
            hash:{sku_id_2:count, sku_id_5:count, sku_id_3: 1}
            set:{sku_id_2}
            """
            # hincrby()   增量的同时做添加
            # pl.hincrby(name, 3, 1)
            # 如果要添加到key在hash字典中已存在,就会把key对应的value与新添加的value做累加,然后存储累加后的值
            # 如果要添加到key在hash字典中不存在,那么就是一个新增行为
            pl.hincrby('cart_%s' % user.id, sku_id, count)
            # 判断当前商品有没有勾选,如果是勾选在添加到set集合
            if selected:
                # 把勾选商品的sku_id添加到set集合
                pl.sadd('selected_%s' % user.id, sku_id)

            # 执行管道
            pl.execute()

            # 响应
            # return Response(serializer.data, status=status.HTTP_201_CREATED)

        else:
            # 未登录用户操作cookie购物车数据
            # 先获取cookie购物车数据
            cart_str = request.COOKIES.get('cart')
            # 判断购物车中cookie中是否有数据
            if cart_str:
                # 先把str的购物车数据转换为bytes
                cart_str_bytes = cart_str.encode()
                # 再调用base64.b64decode() 把bytes类型的字符串转换成bytes类型的ASSCII
                cart_str_asscii = base64.b64decode((cart_str_bytes))
                # 再调用pickle.loads() 把bytes类型的ASSCII 转换为字典
                cart_dict = pickle.loads(cart_str_asscii)
            else:
                cart_dict = {}  # 如果是第一次添加准备一个新字典

            """
            {
                sku_id: {
                   "count": xxx,  // 数量
                   "selected": True  // 是否勾选
                },
                sku_id: {
                   "count": xxx,
                   "selected": False
                },
                ...
            }
            """
            # 判断当前要添加的sku_id 在cookie字典中是否已经存在,如果存在,取出sku_id的count,和本次count累加
            if sku_id in cart_dict:
                origin_count = cart_dict[sku_id]['count']
                count += origin_count
            # 添加cookie购物车字典
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }
            # 把cookie字典转换成 bytes类型的ASSCII    pickle.dumps()
            cart_str_asscii = pickle.dumps(cart_dict)
            # 调用base64.b64encode()  把bytes类型的ASSCII转换成bytes类型的str
            cart_str_bytes = base64.b64encode(cart_str_asscii)
            # 调用decode()把bytes类型str 转换成str
            cart_str = cart_str_bytes.decode()

            # 设置到cookie
            # response = Response(serializer.data, status=status.HTTP_201_CREATED)
            response.set_cookie('cart', cart_str)

            # 响应
            # return response
        return response

    def get(self, request):
        """购物车查询"""

        try:
            user = request.user
        except:
            user = None

        if user:
            # 登陆用户获取redis购物车数据
            # 创建redis连接
            redis_conn = get_redis_connection('cart')
            # 取出hash字典中的数据{sku_id:count}
            cart_redis = redis_conn.hgetall('cart_%s' % user.id)
            # 取出set集合中的数据{sku_id}
            selected_ids = redis_conn.smembers('selected_%s' % user.id)
            # 定义一个字典,进行把redis购物车数据转换成和cookie购物车数据格式一样
            cart_dict = {}  # 将来和cookie字典模型及变量名一样
            for sku_id_bytes, count_bytes in cart_redis.items():
                cart_dict[int(sku_id_bytes)] = {
                    'count': int(count_bytes),
                    'selected': sku_id_bytes in selected_ids
                }

        else:
            # 未登录用户获取cookie购物车数据
            # 获取cookie数据
            cart_str = request.COOKIES.get('cart')
            if cart_str:
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:
                # cart_dict = {}
                return Response(status=status.HTTP_400_BAD_REQUEST)

        # 获取sku_id对应的模型并且做相应的序列化
        sku_ids = cart_dict.keys()
        skus = SKU.objects.filter(id__in=sku_ids)
        # 补充count和selected字段
        for sku in skus:
            sku.count = cart_dict[sku.id]['count']  # 在数据后面多加逗号会自动变元祖
            sku.selected = cart_dict[sku.id]['selected']

        # 响应
        serializer = CartSKUSerializer(skus, many=True)
        return Response(serializer.data)

    def put(self, request):
        """购物车修改"""
        # 创建序列列化器器对象并验证字段
        serializer = CartSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        # 获取校验后的参数
        sku_id = serializer.validated_data.get('sku_id')
        count = serializer.validated_data.get('count')
        selected = serializer.validated_data.get('selected')

        try:
            user = request.user
        except:
            user = None
        response = Response(serializer.data)
        if user:
            # 登录用户修改redis购物车数据
            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')
            # 创建管道
            pl = redis_conn.pipeline()
            # 直接设置hash字典   如果sku_id在hash字典中,直接覆盖
            pl.hset('cart_%s' % user.id, sku_id, count)
            # 判断selected是勾选还是未勾选
            if selected:
                # 如果勾选把当前sku_id添加到set集合中
                pl.sadd('selected_%s' % user.id, sku_id)
            else:
                # 如果未勾选把当前sku_id从set集合中移除
                pl.srem('selected_%s' % user.id, sku_id)
            # 执行管道
            pl.execute()
            # 如果勾选

            # 响应
            # return Response(serializer.data)

        else:
            # 未登录用户修改cookie购物车数据
            # 获取cookie购物车数据
            cart_str = request.COOKIES.get('cart')
            # 把字符串类型的购物车数据转换成字典
            cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            # 对cart_dict 直接重新赋值
            cart_dict[sku_id] = {
                'count': count,
                'selected': selected
            }
            # 把购物车字典转换成字符串
            cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
            # 设置cookie
            response.set_cookie('cart', cart_str)
            # 响应
            # return response
        # 响应
        return response

    def delete(self, request):
        """购物车删除"""

        # 创建序列列化器器对象并验证字段
        serializer = CartDeleteSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        # 获取校验后的参数
        sku_id = serializer.validated_data.get('sku_id')

        # 获取user,用于判断用户是否登陆
        try:
            user = request.user
        except Exception:
            user = None

        # 判断用户是否登陆
        if user:
            # 登录用户修改redis购物车数据
            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')
            # 创建管道
            pl = redis_conn.pipeline()
            # 直接设置hash字典   如果sku_id在hash字典中,直接覆盖
            pl.hdel('cart_%s' % user.id, sku_id)
            pl.srem('selected_%s' % user.id, sku_id)
            pl.execute()

            # 删除结束后，没有响应的数据，只需要响应状态码即可
            return Response(status=status.HTTP_204_NO_CONTENT)
        else:
            # ⽤用户未登录，cookie操作购物⻋车
            cart_str = request.COOKIES.get('cart')
            if cart_str:  # ⽤户操作过cookie购物⻋
                # 将cart_str转成bytes,再将bytes转成base64的bytes,最后将bytes转字典
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))
            else:  # ⽤用户从没有操作过cookie购物⻋车
                # cart_dict = {}   # 如果返回的数据没有,空字典可能会出问题,不要包成字典
                return Response(status=status.HTTP_400_BAD_REQUEST)

            # 创建响应对象
            response = Response(status=status.HTTP_204_NO_CONTENT)
            if sku_id in cart_dict:
                del cart_dict[sku_id]
                # 将字典转成bytes,再将bytes转成base64的bytes,最后将bytes转字符串串
                cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()
                # 响应结果并将购物⻋车数据写⼊入到cookie
                response.set_cookie('cart', cookie_cart_str)
            return response


class CartSelectAllView(APIView):
    """购物⻋全选 """

    def perform_authentication(self, request):
        """重写此方法可以延后/禁用认证"""
        # 当第一次写 request.user /request.auth就会再去执行认证逻辑
        pass

    def put(self, request):
        """购物⻋车商品全选"""

        # 序列化器使用
        serializer = CartSelectAllSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        selected = serializer.validated_data['selected']

        try:
            user = request.user
        except Exception:
            user = None

        # response = Response({'message':'OK'})
        response = Response(serializer.data)  # Response({key:value})  前段并没有用,只是restful风格

        if user:
            # 用户已经登陆,操作redis数据库
            # 创建redis连接对象
            redis_conn = get_redis_connection('cart')

            # 获取hash字典中所有购物车商品sku_id:conut {b'1': b'2', b'16':b'5'}
            redis_dict_cart = redis_conn.hgetall('cart_%s' % user.id)
            # 将购物车中hash所有sku_id添加或者删除
            sku_ids = redis_dict_cart.keys()
            if selected:
                redis_conn.sadd('selected_%s' % user.id, *sku_ids)
            else:
                redis_conn.srem('selected_%s' % user.id, *sku_ids)  # 此处redis中可删可不删,因此可以注释

            # 响应结果
            # return Response({'message':'OK'})
            # return Response(serializer.data)

        else:
            # 用户未登录,操作cookie购物车
            cart_str = request.COOKIES.get('cart')

            # 读取cookie中的购物车数据
            if cart_str:
                # cart_str_bytes = cart_str.encode()
                # redis_dict_bytes = base64.b64decode(cart_str_bytes)
                # cart_dict = pickle.loads(redis_dict_bytes)
                cart_dict = pickle.loads(base64.b64decode(cart_str.encode()))  # 可以这样写,代替前面注释的
            else:
                # cart_dict = {}
                return Response(status=status.HTTP_400_BAD_REQUEST)
            # 遍历购物车字典将所有sku_id对应的selected设置为用户传入的selected
            for sku_id in cart_dict:
                cart_dict[sku_id]['selected'] = selected
                # cart_dict[sku_id]['selected'] = True   # 也可以这样写

            # 字典推导式,可代替上面for循环
            # {i for i in range(5) if i % 2 != 0}
            # {key: value for if for if}
            # cart_dict = {sku_id: {'count': cart_dict[sku_id]['count'], 'selected': selected} for sku_id in cart_dict}

            # 构造购物车字符串
            # cookie_cart_dict_bytes = pickle.dumps(cart_dict)
            # cookie_cart_str_bytes = base64.b64encode(cookie_cart_dict_bytes)
            # cookie_cart_str = cookie_cart_str_bytes.decode()
            cookie_cart_str = base64.b64encode(pickle.dumps(cart_dict)).decode()  # 此行为上面注释三行的简写

            # cookie中写入购物车字符串
            # response = Response({'message':'OK'})
            response.set_cookie('cart', cookie_cart_str)
            # return response

        # 响应
        return response
